#include "nemu.h"
#include "burst.h"
#include "memory/cache.h"

#include <stdlib.h>
#include <time.h>

void init_cache() {
	int i;
	for(i = 0; i < CACHE_L1_SET_NUM * CACHE_L1_WAY_NUM; i ++) {
		cache_L1[i].valid_val = false;
	}
	for(i = 0; i < CACHE_L2_SET_NUM * CACHE_L2_WAY_NUM; i ++) {
		cache_L2[i].dirty_val = false;
		cache_L2[i].valid_val = false;
	}
	return;
}

void ddr3_read_me(hwaddr_t addr, void* data);

int read_cache_L1(hwaddr_t addr) {
	uint32_t set_index = ( (addr >> CACHE_BLOCK_BIT) & (CACHE_L1_SET_NUM - 1) );
	uint32_t tag = ( addr >> (CACHE_BLOCK_BIT + CACHE_L1_SET_BIT) );
	int way_index;
	int whole_begin_way_index = set_index * CACHE_L1_WAY_NUM;
	int whole_end_way_index = (set_index + 1) * CACHE_L1_WAY_NUM;
	int way_index_L2 = read_cache_L2(addr);

	for(way_index = whole_begin_way_index; way_index < whole_end_way_index; way_index ++)
		if(cache_L1[way_index].valid_val && cache_L1[way_index].tag == tag) return way_index;
	srand(time(0));
	way_index = whole_begin_way_index + rand() % CACHE_L1_WAY_NUM;
	memcpy(cache_L1[way_index].data, cache_L2[way_index_L2].data, CACHE_BLOCK_SIZE);

	cache_L1[way_index].valid_val = true;
	cache_L1[way_index].tag = tag;

	return way_index;
}

void ddr3_write_me(hwaddr_t addr, void* data, uint8_t* mask);

int read_cache_L2(hwaddr_t addr) {
	uint32_t set_index = ( (addr >> CACHE_BLOCK_BIT) & (CACHE_L2_SET_NUM - 1) );
	uint32_t tag = ( addr >> (CACHE_BLOCK_BIT + CACHE_L2_SET_BIT) );
	uint32_t block_start = ( (addr >> CACHE_BLOCK_BIT) << CACHE_BLOCK_BIT );
	int way_index;
	int whole_begin_way_index = set_index * CACHE_L2_WAY_NUM;
	int whole_end_way_index = (set_index + 1) * CACHE_L2_WAY_NUM;
	int i;

	for(way_index = whole_begin_way_index; way_index < whole_end_way_index; way_index ++)
		if(cache_L2[way_index].valid_val && cache_L2[way_index].tag == tag) return way_index;
	srand(time(0));
	way_index = whole_begin_way_index + rand() % CACHE_L2_WAY_NUM;
	if(cache_L2[way_index].valid_val && cache_L2[way_index].dirty_val) {
		uint8_t ans[BURST_LEN << 1];
		memset(ans, 1, sizeof(ans));
		uint32_t block_start_x = (cache_L2[way_index].tag << (CACHE_L2_SET_BIT + CACHE_BLOCK_BIT)) | (set_index << CACHE_BLOCK_BIT);
	    	for (i = 0; i < CACHE_BLOCK_SIZE / BURST_LEN; i++)
      			ddr3_write_me(block_start_x + BURST_LEN * i, cache_L2[way_index].data + BURST_LEN * i, ans);
	}
  	for (i = 0; i < CACHE_BLOCK_SIZE / BURST_LEN; i++)
    		ddr3_read_me(block_start + BURST_LEN * i, cache_L2[way_index].data + BURST_LEN * i);

	cache_L2[way_index].valid_val = true;
  	cache_L2[way_index].dirty_val = false;
  	cache_L2[way_index].tag = tag;
  	
	return way_index;
}

void dram_write(hwaddr_t addr, size_t len, uint32_t data);

void write_cache_L1(hwaddr_t addr, size_t len, uint32_t data) {
  	uint32_t set_index = ((addr >> CACHE_BLOCK_BIT) & (CACHE_L1_SET_NUM - 1));
  	uint32_t tag = (addr >> (CACHE_BLOCK_BIT + CACHE_L1_SET_BIT));
 	uint32_t block_bias = addr & (CACHE_BLOCK_SIZE - 1);
  	int way_index;
  	int whole_begin_way_index = set_index * CACHE_L1_WAY_NUM;
  	int whole_end_way_index = (set_index + 1) * CACHE_L1_WAY_NUM;
  	for (way_index = whole_begin_way_index; way_index < whole_end_way_index; way_index++) {
    		if (cache_L1[way_index].valid_val && cache_L1[way_index].tag == tag) {
      			if (block_bias + len > CACHE_BLOCK_SIZE) {
        			dram_write(addr, CACHE_BLOCK_SIZE - block_bias, data);
        			memcpy(cache_L1[way_index].data + block_bias, &data, CACHE_BLOCK_SIZE - block_bias);
        			write_cache_L2(addr, CACHE_BLOCK_SIZE - block_bias, data);
        			write_cache_L1(addr + CACHE_BLOCK_SIZE - block_bias, len - (CACHE_BLOCK_SIZE - block_bias), data >> (CACHE_BLOCK_SIZE - block_bias));
      			}	 
			else {
        			dram_write(addr, len, data);
        			memcpy(cache_L1[way_index].data + block_bias, &data, len);
        			write_cache_L2(addr, len, data);
      			}
      	
			return;
    		}
  	}
  	write_cache_L2(addr, len, data);
  	return;
}

void write_cache_L2(hwaddr_t addr, size_t len, uint32_t data) {
  	uint32_t set_index = ((addr >> CACHE_BLOCK_BIT) & (CACHE_L2_SET_NUM - 1));
  	uint32_t tag = (addr >> (CACHE_BLOCK_BIT + CACHE_L2_SET_BIT));
  	uint32_t block_bias = addr & (CACHE_BLOCK_SIZE - 1);
  	int way_index;
  	int whole_begin_way_index = set_index * CACHE_L2_WAY_NUM;
  	int whole_end_way_index = (set_index + 1) * CACHE_L2_WAY_NUM;
  	for (way_index = whole_begin_way_index; way_index < whole_end_way_index; way_index++) {
    		if (cache_L2[way_index].valid_val && cache_L2[way_index].tag == tag) {
      			cache_L2[way_index].dirty_val = true;
      			if (block_bias + len > CACHE_BLOCK_SIZE) {
        			memcpy(cache_L2[way_index].data + block_bias, &data, CACHE_BLOCK_SIZE - block_bias);
        			write_cache_L2(addr + CACHE_BLOCK_SIZE - block_bias, len - (CACHE_BLOCK_SIZE - block_bias), data >> (CACHE_BLOCK_SIZE - block_bias));
		      	} 
			else memcpy(cache_L2[way_index].data + block_bias, &data, len);
      			return;
    		}
  	}
  	way_index = read_cache_L2(addr);
  	cache_L2[way_index].dirty_val = true;
  	memcpy(cache_L2[way_index].data + block_bias, &data, len);
  	return;
}
